package com.lifeonwalden.completionsuggester;

import com.lifeonwalden.completionsuggester.common.Config;
import com.lifeonwalden.completionsuggester.common.SuggesterNode;
import com.lifeonwalden.completionsuggester.support.SuggestFieldFetcher;
import com.lifeonwalden.completionsuggester.support.impl.SuggestFieldBeanFetcher;
import com.lifeonwalden.completionsuggester.support.impl.SuggestFieldMapFetcher;

import java.util.List;
import java.util.Map;
import java.util.Optional;

public class Suggester<T> {
    private SuggesterNode<T> root;
    private Config config;
    private SuggestFieldFetcher<T> fieldFetcher;

    public Suggester(List<T> items, Config config) {
        this.config = config;

        init(items);
    }

    public void add(T item) {
        synchronized (this) {
            build(item);
        }
    }

    public void remove(T item) {
        synchronized (this) {
            config.getSuggesterFields().forEach(field -> {
                Optional<char[][]> _fieldAlphabet = this.config.getIndexMetaProvider().get(this.fieldFetcher.fetch(item, field).trim().toLowerCase());
                if (_fieldAlphabet.isPresent()) {
                    char[][] fieldAlphabet = _fieldAlphabet.get();
                    for (char[] alphabet : fieldAlphabet) {
                        for (int i = 0; i < alphabet.length; i++) {
                            this.root.remove(item, alphabet, i - 1);
                        }
                    }
                }
            });
        }
    }

    public void refresh(List<T> items) {
        synchronized (this) {
            final long flushStamp = System.currentTimeMillis();
            items.forEach(item -> {
                config.getSuggesterFields().forEach(field -> {
                    Optional<char[][]> _fieldAlphabet = this.config.getIndexMetaProvider().get(this.fieldFetcher.fetch(item, field).trim().toLowerCase());
                    if (_fieldAlphabet.isPresent()) {
                        char[][] fieldAlphabet = _fieldAlphabet.get();
                        for (char[] alphabet : fieldAlphabet) {
                            for (int i = 0; i < alphabet.length; i++) {
                                root.refresh(item, alphabet, i - 1, flushStamp);
                            }
                        }
                    }
                });
            });

            this.root.clearEmptyNode();
            this.root.resize(flushStamp);
        }
    }

    public List<T> suggest(String question) {
        if (null != question) {
            return this.root.suggest(question.toLowerCase(), 0);
        } else {
            return this.root.suggest(null, 0);
        }
    }

    private void init(List<T> items) {
        this.root = new SuggesterNode(this.config);
        T _item = items.get(0);

        if (_item instanceof Map) {
            this.fieldFetcher = new SuggestFieldMapFetcher<>();
        } else {
            this.fieldFetcher = new SuggestFieldBeanFetcher<>(_item.getClass(), config.getSuggesterFields());
        }

        items.forEach(item -> build(item));
    }

    private void build(T item) {
        config.getSuggesterFields().forEach(field -> {
            Optional<char[][]> _fieldAlphabet = this.config.getIndexMetaProvider().get(this.fieldFetcher.fetch(item, field).trim().toLowerCase());
            if (_fieldAlphabet.isPresent()) {
                char[][] fieldAlphabet = _fieldAlphabet.get();
                for (char[] alphabet : fieldAlphabet) {
                    for (int i = 0; i < alphabet.length; i++) {
                        root.add(item, alphabet, i - 1);
                    }
                }
            }
        });
    }
}
